﻿using Microsoft.Data.Edm;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.OData;
using System.Web.Http.OData.Query;
using WooCoo.Reflection;
//using System.Net.Http.ODataHttpRequestMessageExtensions;
using WooCoo.Tools;

namespace WooCoo.DataPortal.WebHost.ODataServer
{
    /// <summary>
    /// A <see cref="ParameterBindingAttribute"/> to bind parameters of type <see cref="ODataQueryOptions"/> to the OData query from the incoming request.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
    public sealed class WCODataQueryParameterBindingAttribute : ParameterBindingAttribute
    {
        /// <inheritdoc />
        public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
        {
            return new ODataQueryParameterBinding(parameter);
        }

        internal class ODataQueryParameterBinding : HttpParameterBinding
        {
            private static MethodInfo _createODataQueryOptions = typeof(ODataQueryParameterBinding).GetMethod("CreateWCODataQueryOptions");
            private const string CreateODataQueryOptionsCtorKey = "MS_CreateWCODataQueryOptionsOfT";

            public ODataQueryParameterBinding(HttpParameterDescriptor parameterDescriptor)
                : base(parameterDescriptor)
            {
            }

            [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Response disposed later")]
            public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
            {
                if (actionContext == null)
                {
                    throw Error.ArgumentNull("actionContext");
                }

                HttpRequestMessage request = actionContext.Request;

                if (request == null)
                {
                    throw Error.Argument("ActionContextMustHaveRequest", "actionContext");
                }

                HttpActionDescriptor actionDescriptor = actionContext.ActionDescriptor;

                if (actionDescriptor == null)
                {
                    throw Error.Argument("ActionContextMustHaveDescriptor", "actionContext");
                }

                HttpConfiguration configuration = request.GetConfiguration();

                if (configuration == null)
                {
                    throw Error.Argument("RequestMustContainConfiguration","actionContext");
                }

                // Get the entity type from the parameter type if it is ODataQueryOptions<T>.
                // Fall back to the return type if not. Also, note that the entity type from the return type and ODataQueryOptions<T> 
                // can be different (example implementing $select or $expand).
                IEdmModel model = request.GetEdmModel();
                Type entityClrType = EdmLibHelpers.GetClrType(request.GetODataPath().EntitySet.ElementType, model);
                //IEdmModel model = request.GetEdmModel() ?? actionDescriptor.GetEdmModel(entityClrType);
                //Type entityClrType = GetEntityClrTypeFromParameterType(Descriptor) ?? GetEntityClrTypeFromActionReturnType(actionDescriptor);
                ODataQueryContext entitySetContext = new ODataQueryContext(
                    model,
                    entityClrType
                    );

                Func<ODataQueryContext, HttpRequestMessage, WCODataQueryOptions> createODataQueryOptions =
                    (Func<ODataQueryContext, HttpRequestMessage, WCODataQueryOptions>)Descriptor.Properties.GetOrAdd(CreateODataQueryOptionsCtorKey, _=>
                    {
                        return Delegate.CreateDelegate(typeof(Func<ODataQueryContext, HttpRequestMessage, WCODataQueryOptions>), _createODataQueryOptions.MakeGenericMethod(entityClrType));
                    });

                WCODataQueryOptions parameterValue = createODataQueryOptions(entitySetContext, request);
                SetValue(actionContext, parameterValue);

                return TaskHelpers.Completed();
            }

            public static WCODataQueryOptions<T> CreateWCODataQueryOptions<T>(ODataQueryContext context, HttpRequestMessage request)
            {
                return new WCODataQueryOptions<T>(context, request);
            }
        }
    }
}
